// ValidationAspects
// Written by Mike Saunders
// Released under Microsoft Public License (Ms-PL)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using ValidationAspects.Core;
using ValidationAspects.Sdk;

namespace ValidationAspects
{
	public static class ParameterValidationRegistry
	{
		private static readonly ValidationRegistry<ParameterInfo> registry = new ValidationRegistry<ParameterInfo>(pi => pi.ParameterType,
		                                                                                                           pi =>
		                                                                                                           ((ValidatorAttribute[])pi.GetCustomAttributes(typeof(ValidatorAttribute), false)).Select(
		                                                                                                           	a => a.Factory));

		private static void EnforceCanValidate(ParameterInfo parameterInfo)
		{
			if (!CanValidate(parameterInfo))
			{
				throw new ValidationException(string.Format("Validation cannot be applied to parameter [name:{0}] [method:{1}] [type:{2}]. Parameter must not declare the out modifier.", parameterInfo.Name,
				                                            parameterInfo.Member.Name, parameterInfo.Member.DeclaringType));
			}
		}

		public static bool CanValidate(this ParameterInfo parameterInfo)
		{
			Enforce.IsNotNull(parameterInfo, "parameterInfo");
			return !parameterInfo.IsOut;
		}

		public static void ClearCache()
		{
			registry.ClearCache();
		}

		public static void RevertValidation(this ParameterInfo parameterInfo)
		{
			EnforceCanValidate(parameterInfo);
			registry.RevertValidation(parameterInfo);
		}

		public static void ReplaceValidation(this ParameterInfo parameterInfo, IEnumerable<IValidatorFactory> factories)
		{
			EnforceCanValidate(parameterInfo);
			registry.ReplaceValidation(parameterInfo, factories);
		}

		public static void ReplaceValidation<TValue>(this ParameterInfo parameterInfo, Action<TValue, IValidationContext> validateAction)
		{
			EnforceCanValidate(parameterInfo);
			registry.ReplaceValidation(parameterInfo, validateAction);
		}

		public static void AddValidation(this ParameterInfo parameterInfo, IEnumerable<IValidatorFactory> factories)
		{
			EnforceCanValidate(parameterInfo);
			registry.AddValidation(parameterInfo, factories);
		}

		public static void AddValidation<TValue>(this ParameterInfo parameterInfo, Action<TValue, IValidationContext> validateAction)
		{
			EnforceCanValidate(parameterInfo);
			registry.AddValidation(parameterInfo, validateAction);
		}

		public static IValidator ResolveValidator(this ParameterInfo parameterInfo)
		{
			EnforceCanValidate(parameterInfo);

			try
			{
				return registry.ResolveValidator(parameterInfo);
			}
			catch (ValidationException e)
			{
				throw new ValidationException(
					string.Format("Failed to construct validator for [type:{0}] [method:{1}] [parameter:{2}].", parameterInfo.Member.DeclaringType, parameterInfo.Member.Name, parameterInfo.Name), e);
			}
		}
	}
}